/**
 * PBox2d
 * This is a simple little wrapper to help integrate JBox2d with Processing
 * It doesn't do much right now and, in some ways, limits the user
 * It's an open question as to whether this should really be a library
 * or a set of examples. Right now, it's a little bit of both
 * Daniel Shiffman <http://www.shiffman.net>
 */

package simulations;

import org.jbox2d.common.Transform;
import org.jbox2d.common.Vec2; //import org.jbox2d.common.XForm;
import org.jbox2d.dynamics.Body;
import org.jbox2d.dynamics.BodyDef;
import org.jbox2d.dynamics.World;
import org.jbox2d.dynamics.joints.Joint;
import org.jbox2d.dynamics.joints.JointDef;

import processing.core.PApplet;
import processing.core.PVector;

public class PBox2D {

	PApplet parent;

	// The Box2D world
	public static World world;

	// Variables to keep track of translating between world and screen
	// coordinates
	public float transX;// = 320.0f;
	public float transY;// = 240.0f;
	public static float scaleFactor;// = 10.0f;
	public static float yFlip;// = -1.0f; //flip y coordinate

	PContactListener contactlistener;

	// Construct with a default scaleFactor of 10
	public PBox2D(PApplet p) {
		this(p, 20);
	}

	public PBox2D(PApplet p, float sf) {
		parent = p;
		transX = parent.width / 2;
		transY = parent.height / 2;
		scaleFactor = sf;
		yFlip = -1;
	}

	public void listenForCollisions() {
		contactlistener = new PContactListener(parent);
		world.setContactListener(contactlistener);
	}

	// Change the scaleFactor
	public void setScaleFactor(float scale) {
		scaleFactor = scale;
	}

	// This is the all important physics "step" function
	// Says to move ahead one unit in time
	// Default
	public void step() {
		float timeStep = 1.0f / 60f;
		this.step(timeStep, 10, 10);
	}

	// Custom
	public void step(float timeStep, int velocityIterations,
			int positionIterations) {
		world.step(timeStep, velocityIterations, positionIterations);
	}

	public void setWarmStarting(boolean b) {
		world.setWarmStarting(b);
	}

	public void setContinuousPhysics(boolean b) {
		world.setContinuousPhysics(b);
	}

	// Create a default world with default gravity
	public void createWorld() {
		Vec2 gravity = new Vec2(0.0f, -10.0f);
		createWorld(gravity, true);
		setWarmStarting(true);
		setContinuousPhysics(true);
	}

	public void createWorld(Vec2 gravity, boolean doSleep) {
		createWorld(gravity, doSleep, true, true);
	}

	public void createWorld(Vec2 gravity, boolean doSleep,
			boolean warmStarting, boolean continous) {
		world = new World(gravity, doSleep);
		setWarmStarting(warmStarting);
		setContinuousPhysics(continous);
	}

	// Set the gravity (this can change in real-time)
	public void setGravity(float x, float y) {
		world.setGravity(new Vec2(x, y));
	}

	// These functions are very important
	// Box2d has its own coordinate system and we have to move back and forth
	// between them
	// convert from Box2d world to pixel space
	public Vec2 coordWorldToPixels(Vec2 world) {
		return coordWorldToPixels(world.x, world.y);
	}

	public PVector coordWorldToPixelsPVector(Vec2 world) {
		Vec2 v = coordWorldToPixels(world.x, world.y);
		return new PVector(v.x, v.y);
	}

	public Vec2 coordWorldToPixels(float worldX, float worldY) {
		float pixelX = PApplet
				.map(worldX, 0f, 1f, transX, transX + scaleFactor);
		float pixelY = PApplet
				.map(worldY, 0f, 1f, transY, transY + scaleFactor);
		if (yFlip == -1.0f)
			pixelY = PApplet.map(pixelY, 0f, parent.height, parent.height, 0f);
		return new Vec2(pixelX, pixelY);
	}

	// convert Coordinate from pixel space to box2d world
	public Vec2 coordPixelsToWorld(Vec2 screen) {
		return coordPixelsToWorld(screen.x, screen.y);
	}

	public Vec2 coordPixelsToWorld(PVector screen) {
		return coordPixelsToWorld(screen.x, screen.y);
	}

	public Vec2 coordPixelsToWorld(float pixelX, float pixelY) {
		float worldX = PApplet
				.map(pixelX, transX, transX + scaleFactor, 0f, 1f);
		float worldY = pixelY;
		if (yFlip == -1.0f)
			worldY = PApplet.map(pixelY, parent.height, 0f, 0f, parent.height);
		worldY = PApplet.map(worldY, transY, transY + scaleFactor, 0f, 1f);
		return new Vec2(worldX, worldY);
	}

	// Scale scalar quantity between worlds
	public static float scalarPixelsToWorld(float xy) {
		return xy / scaleFactor;
	}

	public static float scalarWorldToPixels(float val) {
		return val * scaleFactor;
	}

	// Scale vector between worlds
	public Vec2 vectorPixelsToWorld(Vec2 v) {
		Vec2 u = new Vec2(v.x / scaleFactor, v.y / scaleFactor);
		u.y *= yFlip;
		return u;
	}

	public static Vec2 vectorPixelsToWorld(PVector v) {
		Vec2 u = new Vec2(v.x / scaleFactor, v.y / scaleFactor);
		u.y *= yFlip;
		return u;
	}

	public static Vec2 vectorWorldToPixels(Vec2 v) {
		Vec2 u = new Vec2(v.x * scaleFactor, v.y * scaleFactor);
		u.y *= yFlip;
		return u;
	}

	public PVector vectorWorldToPixelsPVector(Vec2 v) {
		PVector u = new PVector(v.x * scaleFactor, v.y * scaleFactor);
		u.y *= yFlip;
		return u;
	}

	// A common task we have to do a lot
	public Body createBody(BodyDef bd) {
		return world.createBody(bd);
	}

	// A common task we have to do a lot
	public Joint createJoint(JointDef jd) {
		return world.createJoint(jd);
	}

	// Another common task, find the position of a body
	// so that we can draw it
	public Vec2 getBodyPixelCoord(Body b) {
		Transform xf = b.getTransform();// b.getXForm();
		return coordWorldToPixels(xf.position);
	}

	public PVector getBodyPixelCoordPVector(Body b) {
		Transform xf = b.getTransform();
		return coordWorldToPixelsPVector(xf.position);
	}

	public void destroyBody(Body b) {
		world.destroyBody(b);
	}

}